package tool

import com.softwaremill.quicklens._
import dao.{AccountDao, CompanyMemberDao, MemberDao, SpecialMemberDao, TestDao, UserDao, WechatMemberDao}
import implicits.Implicits._
import play.api.mvc.{MultipartFormData, Request, RequestHeader}
import utils.Utils

import java.io.{ByteArrayInputStream, File}
import scala.collection.immutable.SeqMap
import scala.concurrent.ExecutionContext.Implicits.global
import models.Tables._

import scala.concurrent.Future
import scala.util.Random
import command.{CommandData, CommandExec}
import org.joda.time.DateTime
import org.mindrot.jbcrypt.BCrypt
import play.api.libs.Files.TemporaryFile
import shared.pojo.Pojo.ChangePassword
import shared.pojo.Role
import shared.pojo.Sample._
import upickle.default._

import javax.imageio.ImageIO
import javax.xml.bind.DatatypeConverter

/**
 * Created by yz on 19/8/2021
 */
trait ProjectTool {

  def parseConfigInfo(configInfo: String) = {
    val lines = configInfo.toLines.map(_.trim)
    val newLinesT = lines.dropWhile(x => !x.trim.startsWith("[")).foldLeft((List[String](), false)) { (tmpX, y) =>
      val (x, canAppend) = tmpX
      if (y.startsWith("[")) {
        (x :+ y, false)
      } else if (y.contains("=")) {
        (x :+ y, true)
      } else if (canAppend) {
        (x.init :+ (x.last + y), canAppend)
      } else {
        tmpX
      }
    }
    newLinesT._1.foldLeft(SeqMap[String, SeqMap[String, List[String]]]()) { (map, y) =>
      if (y.startsWith("[")) {
        val kitName = y.drop(1).dropRight(1)
        map + (kitName -> SeqMap.empty)
      } else {
        val (lastKey, lastV) = map.last
        val values = y.mySplit("=").take(2).map(_.trim)
        if (values.size < 2) {
          map
        } else {
          val newVs = values.last.mySplit(";").map(_.trim)
          val t = (values.head, newVs)
          map.updated(lastKey, lastV + t)
        }
      }
    }.map { case (kitName, configMap) =>
      val newConfigMap = configMap.filter { case (k, vs) =>
        vs.nonEmpty
      }
      (kitName, newConfigMap)
    }.filter { case (kitName, configMap) =>
      configMap.nonEmpty
    }
  }

  def parseConfig(config: String) = {
    val lines = config.toLines.map(_.trim)
    val newLines = lines.dropWhile(x => !x.trim.contains("=")).foldLeft(List[String]()) { (x, y) =>
      if (y.contains("=")) {
        x :+ y
      } else {
        x.init :+ (x.last + y)
      }
    }
    newLines.foldLeft(SeqMap[String, List[String]]()) { (map, y) =>
      val values = y.mySplit("=").take(2).map(_.trim)
      if (values.size < 2) {
        map
      } else {
        val newVs = values.last.mySplit(";").map(_.trim)
        val t = (values.head, newVs)
        map + t
      }
    }.filter { case (k, vs) =>
      vs.nonEmpty
    }
  }

  def deleteKitName(kitMap: SeqMap[String, List[String]], deleteKitInfos: List[(String, String)]): SeqMap[String, List[String]] = {
    deleteKitInfos match {
      case Nil => kitMap
      case x :: xs =>
        val (tableName, kitName) = x
        val newKitMap = kitMap.updatedWith(tableName) { kitNamesOp =>
          kitNamesOp.map { kitNames =>
            kitNames.filter(_ != kitName)
          }
        }.filter { case (tableName, kitNames) =>
          kitNames.nonEmpty
        }
        deleteKitName(newKitMap, xs)
    }
  }

  def deleteRowFilter(map: SeqMap[String, String], deleteInfos: List[(String, String)]): SeqMap[String, String] = {
    deleteInfos match {
      case Nil => map
      case x :: xs =>
        val (tableName, name) = x
        val newKitMap = map.updatedWith(tableName) { kitNameOp =>
          kitNameOp.filter { kitName =>
            kitName != name
          }
        }
        deleteRowFilter(newKitMap, xs)
    }
  }

  def memberSelect(account: String, password: String)(implicit memberDao: MemberDao) = {
    memberDao.selectByAccount(account).map { xOp =>
      xOp.filter { x =>
        BCrypt.checkpw(password, x.password)
      }
    }
  }

  def memberSelectRnRole(account: String, password: String)(implicit memberDao: MemberDao) = {
    Tool.memberSelect(account, password).map { userOp =>
      userOp.map { user =>
        Role.Member
      }
    }
  }

  def companyMemberSelect(account: String, password: String)(implicit companyMemberDao: CompanyMemberDao) = {
    companyMemberDao.selectByAccount(account).map { xOp =>
      xOp.filter { x =>
        BCrypt.checkpw(password, x.password)
      }
    }
  }

  def wechatMemberSelect(account: String, password: String)(implicit wechatMemberDao: WechatMemberDao) = {
    wechatMemberDao.selectOp(account).map { xOp =>
      xOp.filter { x =>
        BCrypt.checkpw(password, x.password)
      }
    }
  }

  def wechatMemberSelectRnRole(account: String, password: String)(implicit wechatMemberDao: WechatMemberDao) = {
    Tool.wechatMemberSelect(account, password).map { userOp =>
      userOp.map { user =>
        Role.WechatMember
      }
    }
  }

  def companyMemberSelectRnRole(account: String, password: String)(implicit companyMemberDao: CompanyMemberDao) = {
    Tool.companyMemberSelect(account, password).map { userOp =>
      userOp.map { user =>
        Role.CompanyMember
      }
    }
  }

  def specialMemberSelect(account: String, password: String)(implicit specialMemberDao: SpecialMemberDao) = {
    specialMemberDao.selectByAccount(account).map { xOp =>
      xOp.filter { x =>
        BCrypt.checkpw(password, x.password)
      }
    }
  }

  def speicalMemberSelectRnRole(account: String, password: String)(implicit specialMemberDao: SpecialMemberDao) = {
    Tool.specialMemberSelect(account, password).map { userOp =>
      userOp.map { user =>
        Role.SpecialMember
      }
    }
  }

  def adminSelect(account: String, password: String)(implicit accountDao: AccountDao) = {
    accountDao.selectOp(account).map { xOp =>
      xOp.filter { x =>
        BCrypt.checkpw(password, x.password)
      }
    }
  }

  def adminSelectRnRole(account: String, password: String)(implicit accountDao: AccountDao) = {
    Tool.adminSelect(account, password).map { userOp =>
      userOp.map { user =>
        Role.Admin
      }
    }
  }

  def specialMemberUpdatePassword(data: ChangePassword.Request)(implicit specialMemberDao: SpecialMemberDao) = {
    Tool.specialMemberSelect(data.account, data.password).flatMap { xOp =>
      xOp.map { x =>
        val newRow = x.copy(password = data.newPassword.hashedPassword)
        specialMemberDao.update(newRow).map { y =>
          true
        }
      }.getOrElse {
        Future {
          false
        }
      }
    }
  }

  def companyMemberUpdatePassword(data: ChangePassword.Request)(implicit companyMemberDao: CompanyMemberDao) = {
    Tool.companyMemberSelect(data.account, data.password).flatMap { xOp =>
      xOp.map { x =>
        val newRow = x.copy(password = data.newPassword.hashedPassword)
        companyMemberDao.update(newRow).map { y =>
          true
        }
      }.getOrElse {
        Future {
          false
        }
      }
    }
  }

  def memberUpdatePassword(data: ChangePassword.Request)(implicit memberDao: MemberDao) = {
    Tool.memberSelect(data.account, data.password).flatMap { xOp =>
      xOp.map { x =>
        val newRow = x.copy(password = data.newPassword.hashedPassword)
        memberDao.update(newRow).map { y =>
          true
        }
      }.getOrElse {
        Future {
          false
        }
      }
    }
  }

  def adminUpdatePassword(data: ChangePassword.Request)(implicit accountDao: AccountDao) = {
    Tool.adminSelect(data.account, data.password).flatMap { xOp =>
      xOp.map { x =>
        val newRow = x.copy(password = data.newPassword.hashedPassword)
        accountDao.update(newRow).map { y =>
          true
        }
      }.getOrElse {
        Future {
          false
        }
      }
    }
  }

  def updatePassword(data: ChangePassword.Request)(implicit accountDao: AccountDao, memberDao: MemberDao,
                                                   specialMemberDao: SpecialMemberDao, companyMemberDao: CompanyMemberDao) = {
    val accountF = adminUpdatePassword(data)
    val memberF = memberUpdatePassword(data)
    val specialMemberF = specialMemberUpdatePassword(data)
    val companyMemberF = companyMemberUpdatePassword(data)
    Future.sequence(List(accountF, memberF, specialMemberF, companyMemberF)).map { bs =>
      bs.reduceLeft(_ || _)
    }

  }

  def productValidCode = {
    ("0" * 11).map { i =>
      (Random.nextInt(10) + '0').toChar
    }
  }

  def getData(content: String) = {
    read[SampleInfo](content)
  }

  def getSampleIdDir(sampleId: String) = {
    new File(Tool.sampleDir, sampleId)
  }

  def getAppendDir(sampleId: String) = {
    new File(Tool.getSampleIdDir(sampleId), "append")
  }

  def getLabAppendDir(sampleId: String) = {
    new File(Tool.getSampleIdDir(sampleId), "lab_append")
  }

  def getAppendIdDir(sampleId: String, id: Int) = {
    new File(Tool.getAppendDir(sampleId), s"${id}")
  }

  def getImagesDir(sampleId: String) = {
    new File(Tool.getSampleIdDir(sampleId), "images").createDirectoryWhenNoExist
  }

  def getCurImagesDir(sampleId: String) = {
    new File(Tool.getSampleIdDir(sampleId), "curImages").createDirectoryWhenNoExist
  }

  def getFolder(appendIdDir: File) = {
    val folders = appendIdDir.myListFiles.map(_.getName.toInt)
    if (folders.isEmpty) {
      "0"
    } else {
      s"${folders.max + 1}"
    }

  }

  def imagesMove(id: String)(implicit request: Request[MultipartFormData[TemporaryFile]]) = {
    val key = s"${id}_files"
    val curImagesDir = Tool.getCurImagesDir(id)
    Tool.filesMoveDir(key, curImagesDir)
    val imagesDir = Tool.getImagesDir(id)
    imagesDir.deleteQuietly
    curImagesDir.renameTo(imagesDir)
    Tool.productThumb(imagesDir)
  }

  def updateTests(rows: List[SampleRow])(implicit testDao: TestDao) = {
    val testRows = rows.flatMap { row =>
      row.tests.transform(reader[List[TestData]]).map { testData =>
        TestRow(row.id, testData.testType, testData.company, info = ujson.Arr(), id = 0,
          updateTime = new DateTime())
      }
    }
    val testRowMap = testRows.map { row =>
      ((row.sampleId, row.testType, row.company), row)
    }.toMap
    val ids = rows.map(_.id)
    testDao.selectAll(ids).flatMap { dbRows =>
      val dbRowMap = dbRows.map { row =>
        ((row.sampleId, row.testType, row.company), row)
      }.toMap
      val needDeleteRows = dbRows.filterNot { row =>
        testRowMap.isDefinedAt((row.sampleId, row.testType, row.company))
      }
      val needInsertRows = testRows.filterNot { row =>
        dbRowMap.isDefinedAt((row.sampleId, row.testType, row.company))
      }
      testDao.deleteAll(needDeleteRows).zip(testDao.insertAll(needInsertRows))
    }
  }

  def base642Image(data: SampleInfo) = {
    val imagesDir = Tool.getImagesDir(data.id)
    data.images.zipWithIndex.foreach { case (x, curI) =>
      val strs = x.split(",")
      val extension = strs.head.split("/").last.split(";").head
      val imageFile = new File(imagesDir, s"image${curI}.${extension}")
      val data = strs.last
      val imageBytes = DatatypeConverter.parseBase64Binary(data)
      val img = ImageIO.read(new ByteArrayInputStream(imageBytes))
      ImageIO.write(img, extension, imageFile)
    }
    Tool.productThumb(imagesDir)
  }

  def productThumb(imagesDir: File) = {
    val commands = imagesDir.allFiles.map { file =>
      s"""
         |convert ${file.getName} -resize 150x150 thumb_${file.getName}
         |""".stripMargin
    }
    val execCommand = CommandExec().exec { b =>
      CommandData(imagesDir, commands)
    }
  }


}
